package com.geeguo.ebuilder.core.localcache.service.impl;

import com.geeguo.ebuilder.core.common.utils.worker.WorkerIdHelper;
import com.geeguo.ebuilder.core.localcache.constants.LocalCacheConstants;
import com.geeguo.ebuilder.core.localcache.event.LocalCacheChangeEventMessage;
import com.geeguo.ebuilder.core.localcache.event.LocalCacheChangeEventPublisher;
import com.geeguo.ebuilder.core.localcache.service.LocalCacheService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.collect.Sets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Service
public class LocalCacheServiceImpl<V> implements LocalCacheService<V> {

    @Autowired(required = false)
    private LocalCacheChangeEventPublisher localCacheChangeEventPublisher;

    private static Map<String, Cache<String, Object>> customCacheMap = new HashMap<>();

    private static final Cache<String, Object> globalCache = Caffeine.newBuilder()
            // 最大数量
            .maximumSize(50000)
            // 最后一次读或写操作后经过指定时间过期
            .expireAfterAccess(30, TimeUnit.MINUTES)
            .build();

    private static Cache<String, Object> getCustomCache(String cacheName) {
        Cache<String, Object> customCache = customCacheMap.get(cacheName);
        if (customCache == null) {
            throw new RuntimeException("自定义缓存" + cacheName + "未初始化");
        }
        return customCache;
    }

    @Override
    public void initCustomCache(String cacheName, long maximumSize, long expireAfterAccess) {
        Cache<String, Object> customCache = customCacheMap.get(cacheName);
        if (customCache != null) {
            throw new RuntimeException("自定义缓存" + cacheName + "重复初始化");
        }
        customCache = Caffeine.newBuilder()
                .maximumSize(maximumSize)
                .expireAfterAccess(expireAfterAccess, TimeUnit.MINUTES)
                .build();
        customCacheMap.put(cacheName, customCache);
    }

    @Override
    public V get(String key) {
        Object value = globalCache.getIfPresent(key);
        if (value != null) {
            return (V) value;
        } else {
            return null;
        }
    }

    @Override
    public Map<String, V> getAll(Iterable<String> keys) {
        Map<String, Object> result = globalCache.getAllPresent(keys);
        if (result != null && !result.isEmpty()) {
            return (Map<String, V>) result;
        } else {
            return null;
        }
    }

    @Override
    public void put(String key, V value) {
        globalCache.put(key, value);
    }

    @Override
    public void putAll(Map<String, V> map) {
        globalCache.putAll(map);
    }

    @Override
    public void remove(String key) {
        globalCache.invalidate(key);
        publishChangeEvent(key);
    }

    @Override
    public void removeAll(Iterable<String> keys) {
        globalCache.invalidateAll(keys);
        publishChangeEvent(keys);
    }

    @Override
    public V get(String cacheName, String key) {
        Object value = null;
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            value = globalCache.getIfPresent(key);
        } else {
            value = getCustomCache(cacheName).getIfPresent(key);
        }
        if (value != null) {
            return (V) value;
        } else {
            return null;
        }
    }

    @Override
    public Map<String, V> getAll(String cacheName, Iterable<String> keys) {
        Map<String, Object> result = null;
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            result = globalCache.getAllPresent(keys);
        } else {
            result = getCustomCache(cacheName).getAllPresent(keys);
        }
        if (result != null && !result.isEmpty()) {
            return (Map<String, V>) result;
        } else {
            return null;
        }
    }

    @Override
    public void put(String cacheName, String key, V value) {
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            globalCache.put(key, value);
        } else {
            getCustomCache(cacheName).put(key, value);
        }
    }

    @Override
    public void putAll(String cacheName, Map<String, V> map) {
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            globalCache.putAll(map);
        } else {
            getCustomCache(cacheName).putAll(map);
        }
    }

    @Override
    public void remove(String cacheName, String key) {
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            globalCache.invalidate(key);
        } else {
            getCustomCache(cacheName).invalidate(key);
        }
        publishChangeEvent(cacheName, key);
    }

    @Override
    public void removeAll(String cacheName, Iterable<String> keys) {
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            globalCache.invalidateAll(keys);
        } else {
            getCustomCache(cacheName).invalidateAll(keys);
        }
        publishChangeEvent(cacheName, keys);
    }

    @Override
    public void removeAllWithoutEvent(String cacheName, Iterable<String> keys) {
        if (cacheName.equals(LocalCacheConstants.GLOBAL_CACHE_NAME)) {
            globalCache.invalidateAll(keys);
        } else {
            getCustomCache(cacheName).invalidateAll(keys);
        }
    }

    private void publishChangeEvent(String key) {
        if (localCacheChangeEventPublisher != null) {
            LocalCacheChangeEventMessage message = new LocalCacheChangeEventMessage();
            message.setWorkerId(WorkerIdHelper.getWorkerId());
            message.setCacheName(LocalCacheConstants.GLOBAL_CACHE_NAME);
            message.setKeys(Set.of(key));
            localCacheChangeEventPublisher.publish(message);
        }
    }

    private void publishChangeEvent(Iterable<String> keys) {
        if (localCacheChangeEventPublisher != null) {
            LocalCacheChangeEventMessage message = new LocalCacheChangeEventMessage();
            message.setWorkerId(WorkerIdHelper.getWorkerId());
            message.setCacheName(LocalCacheConstants.GLOBAL_CACHE_NAME);
            message.setKeys(Sets.newHashSet(keys));
            localCacheChangeEventPublisher.publish(message);
        }
    }

    private void publishChangeEvent(String cacheName, String key) {
        if (localCacheChangeEventPublisher != null) {
            LocalCacheChangeEventMessage message = new LocalCacheChangeEventMessage();
            message.setWorkerId(WorkerIdHelper.getWorkerId());
            message.setCacheName(cacheName);
            message.setKeys(Set.of(key));
            localCacheChangeEventPublisher.publish(message);
        }
    }

    private void publishChangeEvent(String cacheName, Iterable<String> keys) {
        if (localCacheChangeEventPublisher != null) {
            LocalCacheChangeEventMessage message = new LocalCacheChangeEventMessage();
            message.setWorkerId(WorkerIdHelper.getWorkerId());
            message.setCacheName(cacheName);
            message.setKeys(Sets.newHashSet(keys));
            localCacheChangeEventPublisher.publish(message);
        }
    }
}